/*! ========================================================================
** Extended Template and Library
** Thread Abstraction Class Implementation
**
** Copyright (c) 2002 Robert B. Quattlebaum Jr.
**
** This package is free software; you can redistribute it and/or
** modify it under the terms of the GNU General Public License as
** published by the Free Software Foundation; either version 2 of
** the License, or (at your option) any later version.
**
** This package is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
** General Public License for more details.
**
** === N O T E S ===========================================================
**
** This is an internal header file, included by other ETL headers.
** You should not attempt to use it directly.
**
** ========================================================================= */

#ifndef __ETL__THREAD_H_
#define __ETL__THREAD_H_

#define __USE_GNU

#ifdef HAVE_PTHREAD_H
# include <pthread.h>
#endif

#ifdef HAVE_SCHED_H
# include <sched.h>
#endif

#ifdef HAVE_CREATETHREAD
# include <windows.h>
#endif

#if ( defined (HAVE_PTHREAD_CREATE) || defined (HAVE_CLONE) || defined (HAVE_CREATETHREAD) ) && !defined (NO_THREADS)
# define CALLISTO_THREADS
#endif

#define THREAD_ENTRYPOINT

#if defined(CALLISTO_THREADS) && defined(HAVE_PTHREAD_CREATE)
static inline void Yield(void)
{
    sched_yield();
    pthread_testcancel();
}
#else
#ifdef Yield
#undef Yield
#endif
inline void Yield(void) { }
#endif

#ifdef CALLISTO_THREADS

#ifdef HAVE_PTHREAD_CREATE

class Thread
{
public:
    typedef void* entrypoint_return;
private:

    pthread_t thread;
    int *references;
    entrypoint_return(*entrypoint)(void *);
    void *context;
public:
    Thread(void *(*ep)(void *) = NULL, void *context = NULL):
        references(NULL), entrypoint(ep), context(context) { }
    Thread(const Thread &t)
    {
        thread = t.thread;
        references = t.references;
        entrypoint = t.entrypoint;
        context = t.context;

        if (references) {
            (*references)++;
        }
    }
    const Thread &operator=(const Thread &rhs)
    {
        if (references) {
            (*references)--;

            if (*references == 0) {
                stop();
            }
        }

        thread = rhs.thread;
        references = rhs.references;
        entrypoint = rhs.entrypoint;
        context = rhs.context;

        if (references) {
            (*references)++;
        }

        return *this;
    }

    void start(void)
    {
        references = new int;
        *references = 1;
        pthread_create(&thread, NULL, entrypoint, context);
    }

    void stop(void)
    {
        delete references;
        references = NULL;
        void *exit_status;
        pthread_cancel(thread);
        pthread_join(thread, &exit_status);
    }

    static void TestStop()
    {
        pthread_testcancel();
    }

    static void SyncStop()
    {
        int i;
        pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, &i);
    }

    static void AsyncStop()
    {
        int i;
        pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, &i);
    }

    ~Thread()
    {
        if (references) {
            (*references)--;

            if (*references == 0) {
                stop();
            }
        }
    }
};

class Mutex
{
    pthread_mutex_t mutex;
    pthread_t locker;
    int depth;
public:

    Mutex()
    {
        pthread_mutexattr_t attr;
        pthread_mutexattr_init(&attr);
        // #ifdef PTHREAD_PRIO_INHERIT
        // pthread_mutexattr_setprioceiling(&attr,PTHREAD_PRIO_INHERIT);
#ifdef PTHREAD_MUTEX_RECURSIVE
        pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
#endif
        pthread_mutex_init(&mutex, &attr);
        pthread_mutexattr_destroy(&attr);
        locker = 0;
        depth = 0;
    }

    ~Mutex()
    {
        pthread_mutex_destroy(&mutex);
    }

    void Lock(void)
    {
        if (!locker || locker != pthread_self()) {
            pthread_mutex_lock(&mutex);
            locker = pthread_self();
            depth = 0;
            return;
        }

        depth++;
    }

    bool TryLock(void)
    {
        return !(bool) pthread_mutex_trylock(&mutex);
    }

    void UnLock(void)
    {
        if (depth) {
            depth--;
            return;
        }

        pthread_mutex_unlock(&mutex);
        locker = 0;
    }
};

#ifdef HAVE_PTHREAD_RW_LOCK_INIT
class ReadWriteLock
{
    pthread_rwlock_t rwlock;
public:

    ReadWriteLock()
    {
        pthread_rwlock_init(&rwlock, NULL);
    }

    ~ReadWriteLock()
    {
        pthread_rwlock_destroy(&rwlock);
    }

    void LockRead(void)
    {
        pthread_rwlock_rdlock(&rwlock);
    }

    void LockWrite(void)
    {
        pthread_rwlock_wrlock(&rwlock);
    }

    bool TryLockRead(void)
    {
        return !(bool)pthread_rwlock_tryrdlock(&rwlock);
    }

    bool TryLockWrite(void)
    {
        return !(bool)pthread_rwlock_trywrlock(&rwlock);
    }

    void UnLockWrite(void)
    {
        pthread_rwlock_unlock(&rwlock);
    }

    void UnLockRead(void)
    {
        pthread_rwlock_unlock(&rwlock);
    }
};
#else
class ReadWriteLock : public Mutex
{
public:

    ReadWriteLock()
    {  }

    ~ReadWriteLock()
    {  }

    void LockRead(void)
    {
        Lock();
    }

    void LockWrite(void)
    {
        Lock();
    }

    bool TryLockRead(void)
    {
        return TryLock();
    }

    bool TryLockWrite(void)
    {
        return TryLock();
    }

    void UnLockWrite(void)
    {
        UnLock();
    }

    void UnLockRead(void)
    {
        UnLock();
    }
};
#endif

#else // if defined HAVE_PTHREAD
#ifdef HAVE_CREATETHREAD

#ifdef THREAD_ENTRYPOINT
#undef THREAD_ENTRYPOINT
#endif
#define THREAD_ENTRYPOINT	__stdcall
class Thread
{
public:
    typedef unsigned long entrypoint_return;
private:

    unsigned long thread;
    HANDLE handle;
    int *references;

    entrypoint_return(THREAD_ENTRYPOINT *entrypoint)(void *);

    void *context;

    HDC hdc;
    HGLRC hglrc;

    static entrypoint_return THREAD_ENTRYPOINT thread_prefix(void*data)
    {
        Thread *thread = (Thread *)data;

        if (thread->hglrc) {
            wglMakeCurrent(thread->hdc, thread->hglrc);
        }

        return thread->entrypoint(thread->context);
    }

public:
    Thread(entrypoint_return(THREAD_ENTRYPOINT *ep)(void *) = NULL, void *context = NULL):
        references(NULL), entrypoint(ep), context(context) { }
    Thread(const Thread &t)
    {
        thread = t.thread;
        handle = t.handle;
        references = t.references;
        entrypoint = t.entrypoint;
        context = t.context;
        handle = NULL;

        if (references) {
            (*references)++;
        }
    }
    const Thread &operator=(const Thread &rhs)
    {
        if (references) {
            (*references)--;

            if (*references == 0) {
                stop();
            }
        }

        thread = rhs.thread;
        handle = rhs.handle;
        references = rhs.references;
        entrypoint = rhs.entrypoint;
        context = rhs.context;

        if (references) {
            (*references)++;
        }

        return *this;
    }

    void start(void)
    {
        references = new int;
        *references = 1;

        hglrc = wglGetCurrentContext();
        hdc = wglGetCurrentDC();

        handle = CreateThread(
                     NULL,		// Security stuff
                     0,	// STACK
                     thread_prefix,    // thread function
                     (void*)this,                       // thread argument
                     0,                    // creation option
                     &thread                        // thread identifier
                 );
    }

    void stop(void)
    {
        delete references;
        references = NULL;

        TerminateThread(handle, FALSE);
    }

    int wait(void)
    {
        if (handle) {
            WaitForSingleObject(handle, INFINITE);
            CloseHandle(handle);
        }

        return 0;
    }

    static void TestStop()
    {
    }

    static void SyncStop()
    {
    }

    static void AsyncStop()
    {
    }

    ~Thread()
    {
        if (references) {
            (*references)--;

            if (*references == 0) {
                stop();
            }
        }
    }
};

class Mutex
{
    HANDLE handle;
public:

    Mutex()
    {
        handle = CreateMutex(NULL, FALSE, NULL);
    }

    ~Mutex()
    {
        CloseHandle(handle);
    }

    void Lock(void)
    {
        WaitForSingleObject(handle, INFINITE);
    }

    bool TryLock(void)
    {
        return WaitForSingleObject(handle, INFINITE) == WAIT_FAILED;
    }

    void UnLock(void)
    {
        ReleaseMutex(handle);
    }
};

#endif // if defined HAVE_CREATETHREAD
#endif // if defined HAVE_PTHREAD_CREATE
#endif // if defined CALLISTO_THREADS

#if !defined(CALLISTO_THREADS)
// Dummy object used when not threading
class ReadWriteLock
{
public:

    ReadWriteLock() {}
    ~ReadWriteLock() {}
    void LockRead(void) {}
    void LockWrite(void) {}
    bool TryLockRead(void)
    {
        return true;
    }
    bool TryLockWrite(void)
    {
        return true;
    }
    void UnLockRead(void) {}
    void UnLockWrite(void) {}
};

class Mutex
{
public:

    Mutex() {}
    ~Mutex() {}
    void Lock(void) {}
    bool TryLock(void)
    {
        return true;
    }
    void UnLock(void) {}
};

#endif

class Condition : private Mutex
{
    bool flag;
public:
    Condition()
    {
        flag = false;
    }
    ~Condition()
    { }
    void operator()(void)
    {
        flag = true;
    }
    void Wait(void)
    {
        Lock();

        while (!flag) {
            Yield();
        }

        flag = false;
        UnLock();
    }
    void WaitNext(void)
    {
        Lock();
        flag = false;

        while (!flag) {
            Yield();
        }

        UnLock();
    }
};

#endif